#include "lwEnvelope.h"

lwKey *lwEnvelope::addKey( float time, float value )
{
	lwKey *key = new lwKey(time, value);	
	keys.insert(lower_bound(keys.begin(), keys.end(), key), key);
	return key;
}

/*======================================================================
range()
  
Given the value v of a periodic function, returns the equivalent value
v2 in the principal interval [lo, hi].  If i isn't NULL, it receives
the number of wavelengths between v and v2.
	
v2 = v - i * (hi - lo)
	
For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1.
====================================================================== */

float lwEnvelope::range( float v, float lo, float hi, int *i )
{
	float v2, r = hi - lo;
	
	if ( r == 0.0 ) {
		if ( i ) *i = 0;
		return lo;
	}
	
	v2 = lo + v - r * ( float ) floor(( double ) v / r );
	if ( i ) *i = -( int )(( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 ));
	
	return v2;
}

/*======================================================================
hermite()

Calculate the Hermite coefficients.
====================================================================== */

void lwEnvelope::hermite( float t, float *h1, float *h2, float *h3, float *h4 )
{
	float t2, t3;
	
	t2 = t * t;
	t3 = t * t2;
	
	*h2 = 3.0f * t2 - t3 - t3;
	*h1 = 1.0f - *h2;
	*h4 = t3 - t2;
	*h3 = *h4 - t2 + t;
}

/*======================================================================
bezier()

Interpolate the value of a 1D Bezier curve.
====================================================================== */

float lwEnvelope::bezier( float x0, float x1, float x2, float x3, float t )
{
	float a, b, c, t2, t3;
	
	t2 = t * t;
	t3 = t2 * t;
	
	c = 3.0f * ( x1 - x0 );
	b = 3.0f * ( x2 - x1 ) - c;
	a = x3 - x0 - c - b;
	
	return a * t3 + b * t2 + c * t + x0;
}


/*======================================================================
bez2_time()

Find the t for which bezier() returns the input time.  The handle
endpoints of a BEZ2 curve represent the control points, and these have
(time, value) coordinates, so time is used as both a coordinate and a
parameter for this curve type.
====================================================================== */
  
float lwEnvelope::bez2_time( float x0, float x1, float x2, float x3, float time,	float *t0, float *t1 )
{
	float v, t;
	
	t = *t0 + ( *t1 - *t0 ) * 0.5f;
	v = bezier( x0, x1, x2, x3, t );
	if ( fabs( time - v ) > .0001f ) {
		if ( v > time )
			*t1 = t;
		else
			*t0 = t;
		return bez2_time( x0, x1, x2, x3, time, t0, t1 );
	}
	else
		return t;
}
  
  
  /*
  ======================================================================
  bez2()
  
	Interpolate the value of a BEZ2 curve.
  ====================================================================== */
  
float lwEnvelope::bez2( lwKey *key0, lwKey *key1, float time )
{
	float x, y, t, t0 = 0.0f, t1 = 1.0f;
	
	if ( key0->shape == ID_BEZ2 )
		x = key0->time + key0->param[ 2 ];
	else
		x = key0->time + ( key1->time - key0->time ) / 3.0f;
	
	t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time,
		time, &t0, &t1 );
	
	if ( key0->shape == ID_BEZ2 )
		y = key0->value + key0->param[ 3 ];
	else
		y = key0->value + key0->param[ 1 ] / 3.0f;
	
	return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t );
}
  
  
  /*
  ======================================================================
  outgoing()
  
	Return the outgoing tangent to the curve at key0.  The value returned
	for the BEZ2 case is used when extrapolating a linear pre behavior and
	when interpolating a non-BEZ2 span.
  ====================================================================== */
  
float lwEnvelope::outgoing( unsigned int key0, unsigned int key1 )
{
	float a, b, d, t, tout;
	
	switch ( keys[key0]->shape )
	{
	case ID_TCB:
		a = ( 1.0f - keys[key0]->tension )
			* ( 1.0f + keys[key0]->continuity )
			* ( 1.0f + keys[key0]->bias );
		b = ( 1.0f - keys[key0]->tension )
			* ( 1.0f - keys[key0]->continuity )
			* ( 1.0f - keys[key0]->bias );
		d = keys[key1]->value - keys[key0]->value;
		
		
		if ( key0 > 0 )
		{
			t = ( keys[key1]->time - keys[key0]->time ) / ( keys[key1]->time - keys[ key0-1 ]->time );
			tout = t * ( a * ( keys[key0]->value - keys[ key0-1 ]->value ) + b * d );
		}
		else
			tout = b * d;
		break;
		
	case ID_LINE:
		d = keys[key1]->value - keys[key0]->value;
		if ( key0 > 0 )
		{
			t = ( keys[key1]->time - keys[key0]->time ) / ( keys[key1]->time - keys[ key0-1 ]->time );
			tout = t * ( keys[key0]->value - keys[ key0-1 ]->value + d );
		}
		else
			tout = d;
		break;
		
	case ID_BEZI:
	case ID_HERM:
		tout = keys[key0]->param[ 1 ];
		
		if ( key0 > 0 )
			tout *= ( keys[key1]->time - keys[key0]->time ) / ( keys[key1]->time - keys[ key0-1 ]->time );
		
		break;
		
	case ID_BEZ2:
		tout = keys[key0]->param[ 3 ] * ( keys[key1]->time - keys[key0]->time );
		if ( fabs( keys[key0]->param[ 2 ] ) > 1e-5f )
			tout /= keys[key0]->param[ 2 ];
		else
			tout *= 1e5f;
		break;
		
	case ID_STEP:
	default:
		tout = 0.0f;
		break;
	}
	
	return tout;
}
  
  
/*======================================================================
incoming()
  
Return the incoming tangent to the curve at key1.  The value returned
for the BEZ2 case is used when extrapolating a linear post behavior.
====================================================================== */
  
float lwEnvelope::incoming( unsigned int key0, unsigned int key1 )
{
	float a, b, d, t, tin;
	
	switch ( keys[key1]->shape )
	{
	case ID_LINE:
		d = keys[key1]->value - keys[key0]->value;
		
		if ( key1 < keys.size()-1 )
		{
			t = ( keys[key1]->time - keys[key0]->time ) / ( keys[ key1+1 ]->time - keys[key0]->time );
			tin = t * ( keys[ key1+1 ]->value - keys[key1]->value + d );
		}
		else
			tin = d;
		
		break;
		
	case ID_TCB:
		a = ( 1.0f - keys[key1]->tension )
			* ( 1.0f - keys[key1]->continuity )
			* ( 1.0f + keys[key1]->bias );
		b = ( 1.0f - keys[key1]->tension )
			* ( 1.0f + keys[key1]->continuity )
			* ( 1.0f - keys[key1]->bias );
		d = keys[key1]->value - keys[key0]->value;
		if ( key1 < keys.size()-1 ) {
			t = ( keys[key1]->time - keys[key0]->time ) / ( keys[ key1+1 ]->time - keys[key0]->time );
			tin = t * ( b * ( keys[ key1+1 ]->value - keys[key1]->value ) + a * d );
		}
		else
			tin = a * d;
		break;
		
	case ID_BEZI:
	case ID_HERM:
		tin = keys[key1]->param[ 0 ];
		if ( key1 < keys.size()-1 )
			tin *= ( keys[key1]->time - keys[key0]->time ) / ( keys[ key1+1 ]->time - keys[key0]->time );
		break;
		return tin;
		
	case ID_BEZ2:
		tin = keys[key1]->param[ 1 ] * ( keys[key1]->time - keys[key0]->time );
		if ( fabs( keys[key1]->param[ 0 ] ) > 1e-5f )
			tin /= keys[key1]->param[ 0 ];
		else
			tin *= 1e5f;
		break;
		
	case ID_STEP:
	default:
		tin = 0.0f;
		break;
	}
	
	return tin;
}

/*======================================================================
evalEnvelope()

Given a list of keys and a time, returns the interpolated value of the
envelope at that time.
====================================================================== */
  
float lwEnvelope::evaluate( float time )
{
	lwKey *key0, *key1, *skey, *ekey;
	float t, h1, h2, h3, h4, tin, tout, offset = 0.0f;
	int noff;
	int key0index, key1index;
	
	
	/* if there's no key, the value is 0 */
	
	if ( keys.size() == 0 ) return 0.0f;
	
	/* if there's only one key, the value is constant */
	
	if ( keys.size() == 1 )	return keys[0]->value;
	
	/* find the first and last keys */
	
	key0index = 0;
	key1index = keys.size()-1;
	skey = keys[key0index];
	ekey = keys[key1index];
	
	/* use pre-behavior if time is before first key time */
	
	if ( time < skey->time )
	{
		switch ( behavior[ 0 ] )
		{
		case BEH_RESET:
			return 0.0f;
			
		case BEH_CONSTANT:
			return skey->value;
			
		case BEH_REPEAT:
			time = range( time, skey->time, ekey->time, NULL );
			break;
			
		case BEH_OSCILLATE:
			time = range( time, skey->time, ekey->time, &noff );
			if ( noff % 2 )
				time = ekey->time - skey->time - time;
			break;
			
		case BEH_OFFSET:
			time = range( time, skey->time, ekey->time, &noff );
			offset = noff * ( ekey->value - skey->value );
			break;
			
		case BEH_LINEAR:
			tout = outgoing( key0index, key0index+1 ) / ( keys[key0index+1]->time - keys[key0index]->time );

			return tout * ( time - skey->time ) + skey->value;
		}
	}
	
	/* use post-behavior if time is after last key time */
	
	else if ( time > ekey->time ) {
		switch ( behavior[ 1 ] )
		{
		case BEH_RESET:
			return 0.0f;
			
		case BEH_CONSTANT:
			return ekey->value;
			
		case BEH_REPEAT:
			time = range( time, skey->time, ekey->time, NULL );
			break;
			
		case BEH_OSCILLATE:
			time = range( time, skey->time, ekey->time, &noff );
			if ( noff % 2 )
				time = ekey->time - skey->time - time;
			break;
			
		case BEH_OFFSET:
			time = range( time, skey->time, ekey->time, &noff );
			offset = noff * ( ekey->value - skey->value );
			break;
			
		case BEH_LINEAR:
			tin = incoming( key1index-1, key1index ) / ( ekey->time - keys[key1index-1]->time );
			return tin * ( time - ekey->time ) + ekey->value;
		}
	}
	
	/* get the endpoints of the interval being evaluated */
	
	key0index = keys.size()-2;
	key1index = keys.size()-1;
	key0 = keys[key0index];
	key1 = keys[key1index];
	
	/* check for singularities first */
	
	if ( time == key0->time )
		return key0->value + offset;
	else if ( time == key1->time )
		return key1->value + offset;
	
	/* get interval length, time in [0, 1] */
	
	t = ( time - key0->time ) / ( key1->time - key0->time );
	
	/* interpolate */
	
	switch ( key1->shape )
	{
	case ID_TCB:
	case ID_BEZI:
	case ID_HERM:
		tout = outgoing( key0index, key1index );
		tin = incoming( key0index, key1index );
		hermite( t, &h1, &h2, &h3, &h4 );
		return h1 * key0->value + h2 * key1->value + h3 * tout + h4 * tin + offset;
		
	case ID_BEZ2:
		return bez2( key0, key1, time ) + offset;
		
	case ID_LINE:
		return key0->value + t * ( key1->value - key0->value ) + offset;
		
	case ID_STEP:
		return key0->value + offset;
		
	default:
		return offset;
	}
}
